详细讲解如何使用Winsock实现网络通信

您所在的位置:网站首页 怎样连接VIVO t ws2 详细讲解如何使用Winsock实现网络通信

详细讲解如何使用Winsock实现网络通信

2024-07-15 17:34| 来源: 网络整理| 查看: 265

Winsock接口实际上是微软提供的一些列API函数。它都包含在Winsock2.h中。使用的时候我们还要连接函数的导入库文件:WS2_32.lib。具体使用方法如下: #include #pragma comment(lib,"ws2_32.lib") 实现网络通信,一般我们需要一个服务器端和一个客户端。 整个实现通信的一般过程如下:  服务器端:  1.调用WSAStartup函数初始化WSA  2.调用socket函数建立套接字,返回套接字句柄  3.调用bind函数,关联本地地址到套接字句柄  4.调用listen函数进入监听端口状态  5.调用accept函数,等待接受客户端连接  6.客户端连接,accept函数返回有效套接字句柄  7.调用recv或send函数接受或发送数据  8.调用closesocket函数,关闭客户端套接字  9.可循环5——8,进行数据的发送和接受  10.调用closesocket函数,关闭服务器端套接字  11.调用WSACleanup函数释放winsock库  客户端:  1.调用WSAStartup函数初始化WSA  2.调用socket函数建立套接字,返回套接字句柄  3.调用connect函数,和服务器连接  4.调用recv或send函数接受或发送数据  5.可循环3——4进行收发数据  6.调用closesocket函数,关闭套接字  7.调用WSACleanup函数释放winsock库 下面来详细的讲解一下: 第一步在调用Winsock函数之前必须先装载Winsock函数库,使用WSAStartup函数。失败返回SOCKET_ERROR int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData); 第一个参数wVersionRequested是指定想要加载Winsock库的版本。第二个参数指向一个LPWSADATA结构体指针,保存返回的Winsock库的版本信息。 函数成功调用返回0.可以通过调用WSAGetLastError函数获取失败原因。 具体加载Winsock库的代码如下:

WSADATA wsaData; WORD sockVersion=MAKEWORD(2,2);//版本是2.2 if(WSAStartup(sockVersion,&wsaData)!=0) return 0;

  介绍两个小知识点: 1.MAKEWORD函数 这个函数实际上是一个宏,作用是将两个byte类型合并成一个word类型,一个是在高8位,一个是在低8位。 2.套接字 套接字可以看做成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入套接字中,套接字将这段信息发送到另外一个套接字,应用程序通过套接字获取信息。 在完成Winsock库初始化后,我们需要调用socket函数来建立套接字 功能函数的原型: SOCKET socket(int af,int type,int protocol); 参数af指定套接字使用的地址格式,这里只能用AF_INET。 type参数指定套接字的类型,具体可选有三种: SOCK_STREAM:流套接字,使用TCP提供有连接的可靠传输 SOCK_DGRAM:数据报套接字,使用UDP提供的无连接不可靠传输 SOCK_RAW:原始套接字,Winsock接口并不使用某种特定的协议去封装它,而是由程序自行处理数据报和协议首部。 Protocol是配合type使用的,用来指定协议类型。使用TCP,值是IPPROTO_TCP;如果是UDP,使用IPPROTO_UDP。 函数成功,返回一个SOCKET句柄,失败返回INVALID_SOCKET 什么是句柄? 句柄实际上就是一个长整型数据(long),它是Windows用来标识应用程序中建立或使用的对象的一个唯一标识。它就是一个标识符,通过它我们就可以引用它所标识的对象。一般Windows函数总是返回句柄来提供我们对某些对象或资源的访问。

具体创建一个套接字的代码:

SOCKET sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(sListen==INVALID_SOCKET) { printf("socket error!\n"); return 0; }

建立完套接字后,对于服务器端来说,我们接下来要做的是调用bind函数将本地地址和端口绑定到套接字上。对于客户端,这不是必须的,客户端在建立完套接字后可以直接调用connect函数连接服务器,它会替我们自动绑定,指定一个随机的端口。这样的话我们可以不必要调用bind函数进行地址和端口绑定。 bind函数原型: int bind(SOCKET s,const struct sockaddr FAR *addr,int namelen); s指向一个有效的套接字句柄,可以是上面返回的sListen。 addr是一个sockaddr结构体指针,指向要关联的本地地址,结构体具体如下: struct sockaddr_in {  short int sin_family;  unsigned short int sin_port;  struct in_addr sin_addr;  unsigned char sin_zero[8]; }; 1.sin_family值必须设置成AF_INET 2.sin_port指定TCP或UDP通信服务的端口号 关于端口号的使用范围: 一般为避免与其他应用程序或系统应用程序使用的端口号冲突,一般编程使用1024-49151之间的端口号 0-1024由IANA管理,为公共服务保留 1024-49151是普通用户注册端口号 4951-65535是动态端口号 3.sin_addr存储一个32位IP地址。这个结构我们只用了解其中包含的一个联合类型S_un,联合中有一个u_long类型S_addr用来存储32为的二进制数所表示的IP地址。 使用inet_addr函数可以讲一个小数点的十进制IP转换成一个32位二进制IP inet_ntoa,可以进行inet_addr的逆向转换,将一个32位的二进制数IP转换成小数点的十进制字符串IP表示。 最后一个参数namelen指定了sockaddr结构体的大小 4.sin_zero保留,还没有使用 函数成功返回0,失败返回SOCKET_ERROR。绑定本地ip的具体代码:

sockaddr_in sin; sin.sin_family=AF_INET; sin.sin_port=htons(4500);//htons可以将主机的无符号短整型数转换成网络字节序列。 sin.sin_addr.S_un.S_addr=INADDR_ANY;//表示所有可用地址 if(bind(sListen,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR) { printf("bind error!"); return 0; }

完成绑定后就是调用listen函数,进入监听状态。函数原型: int listen(SOCKET s,int backlong); s是套接字句柄 backlong表示监听队列中允许保持的尚未处理的最大连接数量。通俗说就是同时允许的最大连接数。 函数成功返回0,失败返回SOCKET_ERROR。 注意: listen函数只支持连接的套接字,如SOCK_STREAM类型的套接字,基于TCP有链接的套接字。UDP的则不能使用。 具体监听代码:

if(listen(sListen,5)==SOCKET_ERROR) { printf("listen error!"); return 0; }

设置好套接字进入监听状态后,接下来要进入循环,可以使程序在结束一个连接后,继续处理其他连接。首先调用accept函数等待接收客户端连接。 SOCKET accept(SOCKET s,struct sockaddr* addr,int* addrlen); s指向套接字句柄 addr指向sockaddr结构体,用来存放客户端地址信息。 addrlen指向sockaddr长度的指针。 如果是阻塞模式下,accept会一直等待,直到有一个连接到来后才继续往下执行。accept会返回一个连接客户端的套接字句柄。我们可以利用这个套接字句柄进行接收和发送数据。 通过调用send和recv函数 从套接字接收数据: int recv(SOCKET s,char FAR *buf,int len,int flags); 向套接字发送数据 int send(SOCKET s,const char FAR *buf,int len,int flags); 可以看到接收和发送的函数的参数基本相同 s仍然是指向一个有效的套接字句柄 buf指向发送或接收数据的缓冲区 len指向数据缓冲区的长度 flags表示调用方式,一般为0 recv成功返回接收到的字节数,失败返回SOCKET_ERROR 接收或发送完数据后需要调用closesocket关闭accept返回的套接字句柄。这样就完成了一次和客户端的通信。循环完整代码:

// 循环接受客户的连接请求 sockaddr_in remoteAddr; SOCKET sClient; int nAddrLen = sizeof(remoteAddr); char revData[255]; while(TRUE) { // 接受一个新连接 sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen); //accept函数调用失败则继续等待连接。 if(sClient == INVALID_SOCKET) { printf("accept() error"); continue; } //打印出连接者的ip printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //直到收到有效数据时才打印出来 int ret=recv(sClient,revData,255,0); if(ret>0) { //为了防止打印出错,把字符串结尾设成0x00 revData[ret]=0x00; printf(revData); } char *buff="\r\nzy_dreamer,server coming...\r\n"; //发送数据 send(sClient,buff,strlen(buff),0); // 关闭套接字句柄,结束会话 closesocket(sClient); }

 

最后在退出服务器端程序时,需要调用closesocket函数关闭我们自己创建的socket句柄,然后调用WSACleanup函数释放Winsock库。 完整服务器端代码如下:

#include "stdafx.h" #include #pragma comment(lib,"ws2_32") #include int main(int argc, char* argv[]) { WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 2); //加载winsock库 if(WSAStartup(sockVersion, &wsaData) != 0) return 0; // 创建套节字 SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sListen == INVALID_SOCKET) { printf("socket error\n"); return 0; } // 在sockaddr_in结构中装入地址信息 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(4500); // htons函数 将主机的无符号短整形数转换成网络 //字节顺序 sin.sin_addr.S_un.S_addr = INADDR_ANY; // 使套接字和本地地址绑定 if(bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf(" bind error \n"); closesocket(sListen); return 0; } // 设置套接字进入监听模式 if(listen(sListen, 5) == SOCKET_ERROR) { printf("listen error\n"); closesocket(sListen); return 0; } // 循环接受客户的连接请求 sockaddr_in remoteAddr; SOCKET sClient;//保存客户端套接字 int nAddrLen = sizeof(remoteAddr); char revData[255]; while(TRUE) { // 接受一个新连接,阻塞模式下,会一直等待 sClient = accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen); //accept函数调用失败则继续等待下一次连接。 if(sClient == INVALID_SOCKET) { printf("accept() error"); continue; } //打印出连接者的ip printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //直到收到有效数据时才打印出来 int ret=recv(sClient,revData,255,0); if(ret>0) { //为了防止打印出错,把字符串结尾设成0x00 revData[ret]=0x00; printf(revData); } char *buff="\r\nzy_dreamer,server coming...\r\n"; //发送数据 send(sClient,buff,strlen(buff),0); // 关闭套接字句柄,结束会话 closesocket(sClient); } closesocket(sListen); WSACleanup(); return 0; }

 

改进:可以将收发数据的功能放到单独的线程中执行,可以提高程序效率和友好性。

下一篇讲解客户端文章地址:http://blog.csdn.net/zy_dreamer/article/details/8979678



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3